/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 combat_actor.cpp

	$Header: /heroes4/abstract_combat_creature.cpp $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "abstract_combat_creature.h"

#include <assert.h>
#include <math.h>

#include "adaptor_handler.h"
#include "angle.h"
#include "artifact.h"
#include "artifact_prop_spell_charges.h"
#include "artifact_slot.h"
#include "bound_handler.h"
#include "can_cast.h"
#include "check_artifact_influence.h"
#include "creature_array.h"
#include "creature_stack.h"
#include "creature_traits.h"
#include "creature_type.h"
#include "delay_effect.h"
#include "direction.h"
#include "elements_of.h"
#include "enum_operations.h"
#include "format_string.h"
#include "game_time.h"
#include "hero.h"
#include "martyr.h"
#include "missile_type.h"
#include "obstacle_type.h"
#include "random.h"
#include "screen_point.h"
#include "sound.h"
#include "spell_properties.h"
#include "stationary_combat_object.h"
#include "threat_footprint.h"
#include "town_type.h"


// ----------------------------------------------------------------------
// object which can be attacked
// ----------------------------------------------------------------------
bool t_abstract_target::belongs_to( bool defender ) const
{
	return false;
}


// ----------------------------------------------------------------------
// object which can be attacked
// ----------------------------------------------------------------------
bool t_abstract_target::controlled_by( bool defender ) const
{
	return false;
}

bool t_abstract_target::is_active( t_spell spell ) const
{
	return false;
}

// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
t_abstract_combat_creature::t_abstract_combat_creature()
{
	m_original_slot = -1;
	m_belongs_to_defender = false;
	m_has_retaliated = false;
	m_has_moved = false;
	m_number = 0;
	m_permanent_deaths = 0;
	m_shots = 0;
	m_spell_points = 0;
	m_magic_resistance = 0;
}


// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
t_abstract_combat_creature::t_abstract_combat_creature( t_creature_stack* creature, 
													    int				  slot, 
													    bool			  defender,
														t_player const*   owner )
{
	m_creature_stack = creature;
	m_original_slot = slot;
	m_belongs_to_defender = defender;
	m_has_retaliated = false;
	m_has_moved = false;
	m_number = creature->get_number();
	m_permanent_deaths = 0;
	m_shots = creature->get_shots();
	m_spell_points = creature->get_spell_points();
	m_magic_resistance = creature->get_magic_resistance( owner, k_spell_none );
}

// ------------------------------------------------------------
// add an immunity conferred by an artifact
// ------------------------------------------------------------
void t_abstract_combat_creature::add_immunity( t_artifact_spell_immunity const& immunity )
{
	t_spell_immunity_map::iterator index;

	index = m_spell_immunities.find( immunity.spell );
	if (index != m_spell_immunities.end())
		return;
	m_spell_immunities[immunity.spell] = immunity;
}

bool t_abstract_combat_creature::belongs_to( bool defender ) const
{
	return belongs_to_defender() == defender;
}

bool t_abstract_combat_creature::controlled_by( bool defender ) const
{
	return get_controller() == defender;
}

// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
bool t_abstract_combat_creature::can_cast( t_spell spell ) const
{
	// TODO: Handle potions & wands 
	return knows_spell(spell) && m_spell_points >= get_spell_cost(spell);
}

// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
t_town_type t_abstract_combat_creature::get_alignment() const
{
	return m_creature_stack->get_alignment();
}

// -----------------------------------------------------------------------
// get artifact from creature
// -----------------------------------------------------------------------
static t_artifact k_dummy_artifact;

t_artifact const& t_abstract_combat_creature::get_artifact( t_artifact_slot slot ) const
{
	if (is_summoned())
		return k_dummy_artifact;
	return m_creature_stack->get_artifact( slot );
}

// -----------------------------------------------------------------------
// get size of a backpack
// -----------------------------------------------------------------------
int t_abstract_combat_creature::get_backpack_count() const
{
	if (is_summoned())
		return 0;
	return m_creature_stack->get_backpack_count();
}

// ------------------------------------------------------------------------------
// get hero (if any)
// ------------------------------------------------------------------------------
t_hero const* t_abstract_combat_creature::get_const_hero() const
{
	return m_creature_stack->get_hero();
}

// ------------------------------------------------------------------------------
// get controlling player
// ------------------------------------------------------------------------------
bool t_abstract_combat_creature::get_controller() const
{
	return m_belongs_to_defender;
}

// -----------------------------------------------------------------------
// get artifact from backpack
// -----------------------------------------------------------------------
t_artifact const& t_abstract_combat_creature::get_backpack_slot( int slot ) const
{
	return m_creature_stack->get_backpack_slot( slot );
}

// ------------------------------------------------------------------------------
// get movement in combat.
// ------------------------------------------------------------------------------
int t_abstract_combat_creature::get_combat_movement() const
{
	return m_creature_stack->get_combat_movement();
}

// ------------------------------------------------------------------------------
// return the basic creature
// ------------------------------------------------------------------------------
t_creature_stack* t_abstract_combat_creature::get_creature_stack() const
{
	return m_creature_stack;
}

// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
t_creature_type t_abstract_combat_creature::get_creature_type() const
{
	return m_creature_stack->get_creature_type();
}

// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
int t_abstract_combat_creature::get_damage_low() const
{
	return m_creature_stack->get_damage_low();
}

// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
int t_abstract_combat_creature::get_damage_high() const
{
	return m_creature_stack->get_damage_high();
}

// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
float t_abstract_combat_creature::get_defense_basic( bool ranged ) const
{
	return m_creature_stack->get_defense_basic( ranged );
}

float t_abstract_combat_creature::get_defense_bonus( bool ranged ) const
{
	return m_creature_stack->get_defense_bonus( ranged );
}

float t_abstract_combat_creature::get_defense_reduction( bool ranged ) const
{
	return m_creature_stack->get_defense_reduction( ranged );
}

// ------------------------------------------------------------------------------
// get experience value
// ------------------------------------------------------------------------------
int t_abstract_combat_creature::get_experience_value( int number ) const
{
	if (number < 0)
		number = m_number;
	return m_creature_stack->get_experience_value( number );
}

// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
int t_abstract_combat_creature::get_hit_points() const
{
	return m_creature_stack->get_hit_points();
}

// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
int	t_abstract_combat_creature::get_leadership_morale_bonus() const
{
	return m_creature_stack->get_leadership_morale_bonus();
}

// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
int	t_abstract_combat_creature::get_leadership_luck_bonus() const
{
	return m_creature_stack->get_leadership_luck_bonus();
}

// ------------------------------------------------------------------------------
// get non-adjusted luck
// ------------------------------------------------------------------------------
int t_abstract_combat_creature::get_luck() const
{
	return m_creature_stack->get_luck();
}

// -----------------------------------------------------------------------------
// get resistance to hostile spells
// ------------------------------------------------------------------------------

int t_abstract_combat_creature::get_magic_resistance( t_spell spell ) const
{
	int result = 100 - m_magic_resistance;

	if (has_ability( k_ability_spell_vulnerability ))
		return 0;

	if (is_active( k_spell_magic_resistance ))
		result -= result >> 1;

	if (spell != k_spell_none)
	{
		switch (get_spell_alignment( spell ))
		{
			case k_town_life:
				if (has_ability( k_ability_life_protection ))
					result -= result >> 1;
				break;

			case k_town_order:
				if (is_active( k_spell_protection_from_order ))
					result -= result >> 1;
				break;

			case k_town_death:
				if (has_ability( k_ability_death_protection ))
					result -= result >> 1;
				break;

			case k_town_chaos:
				if (has_ability( k_ability_chaos_protection ))
					result -= result >> 1;
				break;

			case k_town_nature:
				if (is_active( k_spell_protection_from_nature ))
					result -= result >> 1;
				break;
		}
	}

	if (result <= 0)
		return 100;

	return 100 - result;
}

// ------------------------------------------------------------------------------
// return appropriate name
// ------------------------------------------------------------------------------
std::string t_abstract_combat_creature::get_name( bool include_article, int number ) const
{
	if (number < 0)
		number = m_number;
	return m_creature_stack->get_name( include_article, number );
}

// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
int t_abstract_combat_creature::get_number() const
{
	return m_number;
}

// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
int t_abstract_combat_creature::get_maximum_spell_points() const
{
	return m_creature_stack->get_maximum_spell_points();
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
float t_abstract_combat_creature::get_offense( bool ranged ) const
{
	return m_creature_stack->get_offense( ranged );
}

// ------------------------------------------------------------------------------
// find the number of creatures before combat
// ------------------------------------------------------------------------------
int t_abstract_combat_creature::get_original_number() const
{
	return m_creature_stack->get_number();
}


// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
t_skill_mastery t_abstract_combat_creature::get_skill( t_skill_type skill ) const
{
	return m_creature_stack->get_skill(skill);
}

// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
t_bitmap_layer_ptr t_abstract_combat_creature::get_portrait( int height, bool force_awake_portrait ) const
{
	return m_creature_stack->get_portrait( height );
}

// ------------------------------------------------------------------------------
// return adjusted speed
// ------------------------------------------------------------------------------
int t_abstract_combat_creature::get_speed() const
{
	return m_creature_stack->get_speed();
}

// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
int t_abstract_combat_creature::get_spell_points() const
{
	return m_spell_points;
}

// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
int t_abstract_combat_creature::get_shots() const
{
	return m_shots;
}

// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
int t_abstract_combat_creature::get_spell_cost( t_spell spell ) const
{
	return m_creature_stack->get_spell_cost( spell );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
int t_abstract_combat_creature::get_spell_power( t_spell spell, int number ) const
{
	// Call normal spell power function
	return get_spell_power( spell, get_grail_data(), number);
}

// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
int t_abstract_combat_creature::get_spell_power( t_spell spell, t_abstract_grail_data_source const & grail_data, int number ) const
{
	return m_creature_stack->get_spell_power( spell, grail_data, m_number );
}

// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
bool t_abstract_combat_creature::has_ability( t_creature_ability ability ) const
{
	return m_creature_stack->has_ability( ability );
}

// ------------------------------------------------------------
// check if creature has an immunity
// ------------------------------------------------------------
bool t_abstract_combat_creature::has_artifact_immunity( t_spell spell, std::string* artifact_name ) const
{
	t_spell_immunity_map::const_iterator index;

	index = m_spell_immunities.find( spell );
	if (index == m_spell_immunities.end())
		index = m_temporary_spell_immunities.find( spell );
	if (index == m_temporary_spell_immunities.end())
		return false;
	if (artifact_name != 0)
		*artifact_name = index->second.artifact_name;
	return true;
}

// ------------------------------------------------------------------------------
// check if two creatures are currently friendly - not the same as !hostile
// usage: one creature can only attack and damage another if is_friendly() returns false
// ------------------------------------------------------------------------------
bool t_abstract_combat_creature::is_friendly( t_abstract_target const& creature ) const
{
	if (&creature == this)
		return true;
	if (is_active( k_spell_berserk ) || creature.is_active( k_spell_berserk ))
		return false;
	return creature.controlled_by( get_controller() )
		&& creature.belongs_to( get_controller() );
}

// ------------------------------------------------------------------------------
// check if two creatures are currently hostile - not the same as !friendly
// usage: one creature only interferes with another (i.e. prevents ranged attacks)
// if the two are hostile.
// A hypnotized creatures is not hostile, but it's not friendly either, because you
// can attack it.
// ------------------------------------------------------------------------------
bool t_abstract_combat_creature::is_hostile( t_abstract_target const& creature ) const
{
	if (&creature == this)
		return false;
	return !creature.controlled_by( get_controller() )
		&& !creature.belongs_to( get_controller() );
}


// ------------------------------------------------------------------------------
// abstract_combat_creature members
// ------------------------------------------------------------------------------
bool t_abstract_combat_creature::knows_spell( t_spell spell ) const
{
	return m_creature_stack->knows_spell( spell );
}

// -----------------------------------------------------------------------------
// get and adjust spell damage
// ------------------------------------------------------------------------------
int t_abstract_combat_creature::modify_spell_damage( t_abstract_combat_creature const* caster,
													 int damage, t_spell spell ) const
{
	int resistance;

	if (caster != 0 && caster->has_ability( k_ability_ignore_wards ))
		resistance = get_magic_resistance( k_spell_none );
	else
		resistance = get_magic_resistance( spell );
	if (resistance >= 100)
		return 0;
	damage = (damage * (100 - resistance) + 99) / 100;
	if (is_active( k_spell_evil_hour ))
		damage += damage >> 2;
	return damage;
}


// -----------------------------------------------------------------------------
// record the losses incurred in combat
// -----------------------------------------------------------------------------
void t_abstract_combat_creature::record_losses( t_creature_array* armies ) const
{
	int dead;

	if (is_summoned())
		return;

	dead = get_original_number() - get_number();
	if (dead <= 0)
		return;

	t_creature_stack_ptr creature_stack = get_creature_stack();
	t_creature_array&    army = armies[m_belongs_to_defender];

	if (get_hero() != 0)
		army.add( creature_stack );
	else
		army.add( get_creature_type(), dead );
}

// -----------------------------------------------------------------------
// remove item from backpack
// -----------------------------------------------------------------------
t_artifact t_abstract_combat_creature::remove_backpack( int slot )
{
	return m_creature_stack->remove_backpack( slot );
}

// ------------------------------------------------------------------------------
// make the in the original army match the current state of the creature
// ------------------------------------------------------------------------------
void t_abstract_combat_creature::update_original_army( t_creature_array** armies )
{
	if (m_original_slot >= 0)
		do_update_original_army( armies );
}

// ------------------------------------------------------------------------------
// If this creature was killed, redistribute artifacts around own army. If army
// has nowhere to put artifacts, put them in "booty" for the other army
// ------------------------------------------------------------------------------
void t_abstract_combat_creature::redistribute_artifacts( t_creature_array** armies, t_artifact_list & booty )
{
	// Only redistribute artifacts from stacks that are going away
	if ( is_summoned() )
	{
		assert( get_backpack_count() == 0 );
		return;
	}

	if ( m_number > 0 )
		return; 

	if ( get_hero() )
		return;

	t_creature_array& army = *armies[m_belongs_to_defender];

	m_creature_stack->redistribute_artifacts( army, booty );
}

// ------------------------------------------------------------------------------
// make the in the original army match the current state of the creature
// ------------------------------------------------------------------------------
void t_abstract_combat_creature::do_update_original_army( t_creature_array** armies )
{
	t_creature_array& army = *armies[m_belongs_to_defender];
	int				  number = army[m_original_slot].get_number();
	t_hero* hero;

	hero = army[m_original_slot].get_hero();
	if (hero != 0)
	{
		hero->set_wounds( get_wounds() );
		hero->set_spell_points( get_spell_points() );
	}

	assert( number == m_creature_stack->get_number() );

	if (m_number >= number)
		return;

	if (hero != 0)
	{
		hero->set_dead( true );
		hero->set_wounds( hero->get_hit_points() );
		return;
	}

	int dead = number - m_number;

	army.remove( dead, m_original_slot );
}

// ------------------------------------------------------------------------------
// read & write
// ------------------------------------------------------------------------------
static bool read_immunity_map( std::streambuf& stream, t_spell_immunity_map& map )
{
	int							count = get<t_uint16>( stream );
	t_spell						spell;
	t_artifact_spell_immunity	immunity;

	map.clear();
	while (count--)
	{
		spell = t_spell( get<t_int16>( stream ));
		immunity.spell = t_spell( get<t_int16>( stream ));
		if (!read_string16( stream, immunity.artifact_name ))
			return false;
		map.insert( std::make_pair( spell, immunity ));
	}
	return true;
}

static bool write_immunity_map( std::streambuf& stream, t_spell_immunity_map const& map )
{
	t_spell_immunity_map::const_iterator index = map.begin();
	t_spell_immunity_map::const_iterator end = map.end();

	put<t_uint16>( stream, map.size() );
	for (; index != end; ++index)
	{
		put<t_int16>( stream, index->first );
		put<t_int16>( stream, index->second.spell );
		if (!write_string16( stream, index->second.artifact_name ))
			return false;
	}
	return true;
}

static int const k_current_version = 1;

bool t_abstract_combat_creature::read( std::streambuf& stream )
{
	int version = get<t_uint16>( stream );
	
	if (version < 0 || version > k_current_version)
		return false;
	m_belongs_to_defender = (get<t_uint8>(stream) != 0);
	m_has_moved = (get<t_uint8>(stream) != 0);
	m_has_retaliated = (get<t_uint8>(stream) != 0);
	m_magic_resistance = get<t_int16>(stream);
	m_number = get<t_uint32>( stream );
	m_original_slot = get<t_int8>(stream);
	m_permanent_deaths = get<t_uint32>( stream );
	m_shots = get<t_uint16>( stream );
	if (!read_immunity_map( stream, m_spell_immunities ))
		return false;
	m_spell_points = get<t_uint32>(stream);
	::read( stream, m_spells, sizeof( m_spells ));
	if (version < 1)
		m_wounds = 0;
	else
		m_wounds = get<t_uint32>( stream );
	return read_immunity_map( stream, m_temporary_spell_immunities );
}

bool t_abstract_combat_creature::write( std::streambuf& stream ) const
{
	put<t_uint16>( stream, k_current_version );
	put<t_uint8>( stream, m_belongs_to_defender );
	put<t_uint8>( stream, m_has_moved );
	put<t_uint8>( stream, m_has_retaliated );
	put<t_int16>( stream, m_magic_resistance );
	put<t_uint32>( stream, m_number );
	put<t_int8>( stream, m_original_slot );
	put<t_uint32>( stream, m_permanent_deaths );
	put<t_uint16>( stream, m_shots );
	if (!write_immunity_map( stream, m_spell_immunities ))
		return false;
	put<t_uint32>( stream, m_spell_points );
	::write( stream, m_spells, sizeof( m_spells ));
	put<t_uint32>( stream, m_wounds );
	return write_immunity_map( stream, m_spell_immunities );
}
